/*
 * @Description: 控制器-api转发
 * @Autor: HuiSir<273250950@qq.com>
 * @Date: 2022-04-22 10:41:24
 * @LastEditTime: 2022-07-07 22:51:38
 */
import {
    useResult, resErr, useToken,
    useRedisPool, useConfig,
    useClassDecorators,
    useMethodDecorators,
    useParameterDecorators
} from 'tcp-micro-service'
import encrypt from '../helpers/encrypt.js'
import UserModel from '../models/User.js'
import {
    SignupDto,
    LoginDto,
    FindUserByIdDto,
} from './Dto/index.js'
const { succ, fail } = useResult()

// 装饰器
const { Controller } = useClassDecorators()
const { Post, Sys, Bind, Get } = useMethodDecorators()
const { Body, Query, Header } = useParameterDecorators()

// 秘钥这里写死，切勿更改，否则用户数据无法解密
const { createToken, decodeToken } = useToken('sadshbzx.mn=2+cb')

// redis连接池
const redisPool = useRedisPool()

// 配置
const { common: { loginTimeout, tokenFieldInHeader, tokenRedisGroup } } = useConfig()

@Controller()
export default class User {

    /**
     * 注册，密码进行加密
     */
    @Post()
    @Bind(SignupDto)
    async signup(@Body() body: SignupDto) {
        const { username, password, sex = 0 } = body

        const findRes = await UserModel.findOne({ username }) //比对

        //不存在
        if (!findRes) {
            const createRes = await UserModel.create({
                username,
                password: encrypt.encode(password),//密码加密
                sex
            }, '-password') //新增,不返回密码

            //返回结果
            return succ({
                msg: '注册成功',
                data: createRes,
            })

        } else {
            resErr.e200('用户名已存在')
        }
    }


    /**
     * 登录
     */
    @Post()
    @Bind(LoginDto)
    async login(@Body() body: LoginDto) {

        const { username, password } = body

        //数据比对
        const res = await UserModel.findOne(
            { username, password: encrypt.encode(password) },
            '-password'
        )

        if (res) {
            const token = createToken(res.id)
            const redisConn = await redisPool.getConn()

            // 这里将token存到redis(key为userid，value为token)
            // 一个用户只有一条缓存，便于查询当前登陆用户数
            // 数据格式应为hash对象，不应该为单纯键值对，否则会污染redis库全局
            // 但hash无法给单条设置过期时间，所以可使用分类存储，命名格式以冒号隔开，如：key=group:key。
            // group可为项目名+token的格式，以区分不同项目。由于要搭建总用户中心，这里暂不做区分
            const saveToken = await redisConn.set(
                `${tokenRedisGroup}:${res.id}`,
                token,
                'EX',
                loginTimeout // 过期时间，单位为s
            )

            // 释放
            redisConn.release()

            //缓存成功则返回，否则报错
            if (saveToken == 'OK') {
                return succ({ msg: '登录成功', data: res, token })
            } else {
                return fail()
            }
        } else {
            resErr.e401('用户名或密码错误')
        }
    }

    /**
     * Token验证
     */
    @Sys()
    async tokenVerify(@Header(tokenFieldInHeader.toLowerCase()) token: string) {
        //没有token
        if (token == void 0) {
            resErr.e403()
            return
        }

        //这里判断token是否合法
        const { id, loginTime } = decodeToken(token)
        if (!id) {
            resErr.e403()
            return
        }

        //使用userid去redis中查询，如果存在且相同，则重置过期时间，next()，如果不存在或不同，则已过期
        const redisConn = await redisPool.getConn()
        const tokenKey = `${tokenRedisGroup}:${id}`
        const redisToken = await redisConn.get(tokenKey)
        if (!redisToken || redisToken != token) {
            redisConn.release() // 释放redis
            resErr.e403('抱歉，您的登陆已过期')
            return
        }

        //与数据库比对是否有此用户
        const currUser = await UserModel.findOne({ id }, '-password')
        if (!currUser) {
            redisConn.release() // 释放redis
            resErr.e403()
            return
        }

        // 重置过期时间
        await redisConn.expire(tokenKey, loginTimeout)
        // 释放redis
        redisConn.release()
        // 返回用户信息
        const data = { ...currUser, loginTime }
        return succ({ data })
    }

    /**
     * 查询单个用户
     * by userid
     */
    @Get()
    @Bind(FindUserByIdDto)
    async getInfoById(@Query('id') id: FindUserByIdDto['id']) {
        const findUser = await UserModel.findOne({ id }, '-password')
        if (findUser) {
            //返回结果
            return succ({ data: findUser })
        } else {
            resErr.e200('用户不存在')
        }
    }
}